﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.IO;

using System.ComponentModel;
using System.Data;
using System.Drawing;

using System.Windows.Forms;
using System.IO.Ports;
using Modbus.Device;
using Modbus.Utility;
using System.Diagnostics;



namespace modBusTest
{
    /// <summary>
    /// The mapfield class that hold information about a modbus register like whether it is a
    /// flaot or a word a description of the register it physical id and its values where it is dirty or not
    /// </summary>
    public class mapFields
    {
        /// <summary>
        /// If the valriable has been modified the value get set
        /// </summary>
        public bool dirty {  set;get; }
        /// <summary>
        /// Storage if the value is a floating point number
        /// </summary>
        public float result_f;
        /// <summary>
        /// Storage if value is a 16-bit word
        /// </summary>
        public ushort result_w;
        /// <summary>
        /// THe name of the parameter
        /// </summary>
        public string paramName;
        /// <summary>
        /// The base register value is the value without a 3 or 4 in from the number
        /// </summary>
        public ushort baseRegister;
        /// <summary>
        /// The full register value with the 3 or 4 in front of it
        /// </summary>
        public int register;
        /// <summary>
        /// A enum parameterType whether it is a float or word
        /// </summary>
        public enum paramType { float_t, word_t };
        /// <summary>
        /// The enum parameterType representing a holding(4x) or a input(3x) register
        /// </summary>
        public enum regType { holding, input };
        /// <summary>
        /// A variable to store if register is float or word
        /// </summary>
        public paramType parameterType;
        /// <summary>
        /// A register to hold whether the parameter is a float on word
        /// </summary>
        public regType registerType;

        public object lockVariable = new object();
        
        /// <summary>
        /// Default constructor just makes sure the dirty bit is false
        /// </summary>
        public mapFields() {
            dirty = false;
        }
        /// <summary>
        /// Constructor takes list of string and then populates rest of fields
        /// currently the size of list should should be 3
        /// register name,register number, floating or word (w/f)
        /// </summary>
        /// <param name="words">a list of strings ito the class</param>
        public mapFields(string[] words)
        {
            dirty = false;
            paramName = words[0];

            //parse the Address
            register = Int32.Parse(words[1]);

            parseRegisterType();

            parseRegisterDataType(words);

            if (registerType == regType.holding)
            {
                baseRegister = (ushort) (register - 40000);
            }

            if (registerType == regType.input)
            {
                baseRegister = (ushort)(register - 30000);
            }

            
        }

        private void parseRegisterDataType(string[] words)
        {
            if (words[2] == "f")
            {
                parameterType = mapFields.paramType.float_t;
            }
            else if (words[2] == "w")
            {
                parameterType = mapFields.paramType.word_t;
            }
            
        }

        private void parseRegisterType()
        {
            if (register >= 40000)
            {
                registerType = mapFields.regType.holding;
            }
            else
            {
                registerType = mapFields.regType.input;
            }
            
        }
        
        
        
        
        
        public string value {
            get{
                lock (lockVariable)
                {

                    if (parameterType == paramType.float_t)
                    {
                        return result_f.ToString();
                    }
                    else
                    {
                        return result_w.ToString();
                    }
                }
            }
            set
            {
                lock (lockVariable)
                {
                    if (parameterType == paramType.float_t)
                    {
                        result_f = float.Parse(value);
                        dirty = true;
                    }
                    else
                    {
                        result_w = ushort.Parse(value);
                        dirty = true;
                    }
                }
            }
        }
    }
    
    public class modbusmap
    {
        public static int plcOffset = 1;
        

        //private List<mapFields> storage = new List<mapFields>();
        public List<mapFields> storage;
        
        private optimizeRange optInputRegRead = new optimizeRange();
        private optimizeRange optHoldRegRead = new optimizeRange();
        private ushort[] holdingRegistersRead = new ushort[10001];
        private ushort[] inputRegistersRead = new ushort[10001];


        

        /// <summary>
        /// Handle to setup up modbus port
        /// </summary>
        public IModbusSerialMaster master { set; get; }
        /// <summary>
        /// slave id with the device you are trying to communicate with
        /// </summary>
        public byte slaveID { set; get; }
        /// <summary>
        /// Read data from file into internal database 
        /// Some issues with .net not being used
        /// </summary>
        /// <param name="fileName">Name of file to read</param>
        public void readData(string fileName){
            storage = new List<mapFields>();
        
            using (StreamReader sr = new StreamReader(fileName))
            {
                string stringToParse;
                while((stringToParse = sr.ReadLine())!=null){
                    string[] words = stringToParse.Split('\t');

                    mapFields tempFile = new mapFields(words);
                    storage.Add(tempFile);
                }
            }
            //populate the opitimize structure
            processHoldingRegister();
            processInputRegister();
            
            //solve for optimal number of modbus command
            optInputRegRead.solve();
            optHoldRegRead.solve();
        
        }

        /// <summary>
        /// This is an altername method to read in data
        /// The .net version was having some problem with some things
        /// </summary>
        /// <param name="filename"></param>
        public void readDataMethod2(string filename)
        {

            storage = new List<mapFields>();

            using (StreamReader sr = new StreamReader(filename))
            {
                string stringToParse = sr.ReadToEnd();

                List<string> items = new List<string>();

                StringBuilder tempString = new StringBuilder();
                for (int i = 0; i < stringToParse.Length-1; i++)
                {
                    if (stringToParse[i] == '\r' && stringToParse[i + 1] == '\n')
                    {
                        items.Add(tempString.ToString());
                        tempString.Clear();
                        i++;
                    }
                    else
                    {
                        tempString.Append(stringToParse[i]);
                    }
                    
                }

                foreach(string item in items)
                {
                    stringToParse = item;
                    string[] words = stringToParse.Split('\t');

                    if (words.Length == 3)
                    {
                        try
                        {
                            mapFields tempFile = new mapFields(words);
                            storage.Add(tempFile);
                        }
                        catch (Exception err)
                        {
                            throw (new Exception(stringToParse + " is not correct",err));
                        }
                    }
                    else
                    {
                        throw new Exception(stringToParse + "is not correct");
                    }
                }
            }
            //populate the opitimize structure
            processHoldingRegister();
            processInputRegister();

            //solve for optimal number of modbus command
            optInputRegRead.solve();
            optHoldRegRead.solve();

        }


        /// <summary>
        /// Return mapField associated with parameter name
        /// </summary>
        /// <param name="paramName">Name of parameter</param>
        /// <returns>Mapfield structure contating detail about parameter</returns>
        public mapFields findParam(string paramName)
        {
            for (int i = 0; i < storage.Count; i++)
            {
                if(storage[i].paramName == paramName){
                    return storage[i];
                }
            }

            throw (new Exception("Param Does Not Exist"));
        }
        /// <summary>
        /// Return mapfield associated with the register
        /// </summary>
        /// <param name="register"></param>
        /// <returns></returns>
        public mapFields findParam(int register)
        {
            for (int i = 0; i < storage.Count; i++)
            {
                if (storage[i].register == register)
                {
                    return storage[i];
                }
            }

            throw (new Exception("Param Does Not Exist"));
        }
        /// <summary>
        /// exposes internal data structure for the modbus commands
        /// </summary>
        /// <returns>returns that internal structure</returns>
        public List <mapFields> getList()
        {
            parseResults();
            return storage;
        }

        public void writeDirty()
        {
            optimizeRange InputWrite=new optimizeRange();
            optimizeRange HoldingWrite=new optimizeRange();
            ushort[] holdingRegistersWrite = new ushort[10001];
            ushort[] inputRegistersWrite = new ushort[10001];
            //var items = from n in storage where (n.dirty == true) && (n.registerType == mapFields.regType.input) && n.parameterType==mapFields.paramType.word_t  select n ;

            //foreach (mapFields item in items)
            //{
            //    InputWrite.addRegister(item.baseRegister);
            //    inputRegistersWrite[item.baseRegister] = item.result_w;
            //}

            //items = from n in storage where (n.dirty == true) && (n.registerType == mapFields.regType.input) && n.parameterType == mapFields.paramType.float_t select n;

            //foreach (mapFields item in items)
            //{
            //    InputWrite.addRegister(item.baseRegister);
            //    InputWrite.addRegister((ushort)(item.baseRegister+1));
            //    byte[] all = BitConverter.GetBytes(item.result_f);
            //    inputRegistersWrite[item.baseRegister]=   BitConverter.ToUInt16(all, 0);
            //    inputRegistersWrite[item.baseRegister+1] = BitConverter.ToUInt16(all, 2);

                
                
            //}

            var items = from n in storage where (n.dirty == true) && (n.registerType == mapFields.regType.holding) && n.parameterType == mapFields.paramType.word_t select n;

            foreach (mapFields item in items)
            {
                HoldingWrite.addRegister((ushort)(item.baseRegister));
                holdingRegistersWrite[item.baseRegister] = item.result_w;
                
            }

            items = from n in storage where (n.dirty == true) && (n.registerType == mapFields.regType.holding) && n.parameterType == mapFields.paramType.float_t select n;

            foreach (mapFields item in items)
            {
                HoldingWrite.addRegister((ushort)(item.baseRegister));
                HoldingWrite.addRegister((ushort)(item.baseRegister + 1));
                byte[] all = BitConverter.GetBytes(item.result_f);
                
                //if t1 or t2 is slected this needs to be flipped
                holdingRegistersWrite[item.baseRegister] = BitConverter.ToUInt16(all, 2);
                holdingRegistersWrite[item.baseRegister + 1] = BitConverter.ToUInt16(all, 0);
            }
            //InputWrite.solve();
            HoldingWrite.solve();
            foreach (pair item in HoldingWrite.getSolution())
            {
                master.WriteMultipleRegisters(slaveID, (ushort) (item.baseRegister - plcOffset), holdingRegistersWrite.Skip(item.baseRegister).Take(item.range).ToArray());
            }

            for (int i = 0; i < storage.Count; i++)
            {
                storage[i].dirty = false;
            }
        }


        private void processInputRegister()
        {
            populateOptimize(30000, optInputRegRead, mapFields.regType.input);
        
        }

        private void processHoldingRegister()
        {
            populateOptimize(40000, optHoldRegRead, mapFields.regType.holding);
        }
        /// <summary>
        /// Function will populate optimize range class 
        /// </summary>
        /// <param name="offset">offset is 30000 or 40000 depending on input or
        /// holding register</param>
        /// <param name="regType">passes in the optimizerange class for ether
        /// input or holding register</param>
        private void populateOptimize(int offset, optimizeRange regType, mapFields.regType type)
        {
            foreach (mapFields item in storage )
            {
                if (item.registerType == type)
                {
                    if (item.parameterType == mapFields.paramType.word_t)
                    {
                        regType.addRegister((ushort)(item.register - offset ));
                    }
                    else if (item.parameterType == mapFields.paramType.float_t)
                    {
                        regType.addRegister((ushort)(item.register - offset));
                        regType.addRegister((ushort)(item.register - offset + 1));
                    }
                }
            }
        }
        /// <summary>
        /// need serial modbus port created and initialized and slave id set
        /// Will go and actually read the data from modbus
        /// </summary>
        /// <returns>return the amount of time taken for command in ms</returns>
        public long getData()
        {
            if (master == null || slaveID == 0)
            {
                throw new Exception("Set port handle and slave ID");
            }
            


            master.Transport.ReadTimeout = 1000;

            // time how long this operation takes 
            Stopwatch timer = new Stopwatch();
            timer.Reset();
            timer.Start();

            getInputRegisterValues();
            getHoldingRegisterValues();
            timer.Stop();

            return timer.ElapsedMilliseconds;
        }
        /// <summary>
        /// function will fetch input register values
        /// </summary>
        private void getInputRegisterValues()
        {
            foreach (pair item in optInputRegRead.getSolution())
            {
                try
                {
                    ushort[] input = master.ReadInputRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);
                    
                    //this sleep is for redeye's 5ms time delay
                    System.Threading.Thread.Sleep(10);
                    
                    
                    for (int i = 0; i < input.Length; i++)
                    {
                        inputRegistersRead[item.baseRegister+i] = input[i];
                    }
                }
                catch (Exception err)
                {
                    Exception extraData = new Exception(err.Message + " InputRegister BaseRegister: " +item.baseRegister.ToString() + " Range: " + item.range.ToString());
                    throw extraData;
                    
                }

            }
        }
        

        /// <summary>
        /// function will go out and get holding register values
        /// </summary>
        private long  getHoldingRegisterValues()
        {
            Stopwatch test = new Stopwatch();
            test.Reset();
            test.Start();
            foreach (pair item in optHoldRegRead.getSolution())
            {

                try
                {
                    ushort[] input = master.ReadHoldingRegisters(slaveID, (ushort)(item.baseRegister - plcOffset), (ushort)item.range);

                    for (int i = 0; i < input.Length; i++)
                    {
                        holdingRegistersRead[item.baseRegister + i] = input[i];
                    }
                }
                catch (Exception err)
                {
                    Exception extraData = new Exception(err.Message + " Holding BaseRegister: " + item.baseRegister.ToString() + " Range: " + item.range.ToString());
                    throw extraData;
                    
                }

                //System.Threading.Thread.Sleep(30);
                
                

            }
            test.Stop();
            return test.ElapsedMilliseconds;
        }

        private void parseResults()
        {
            for (int i=0; i<storage.Count; i++)
            {
                lock (storage[i].lockVariable)
                {

                    if (storage[i].dirty == false)
                    {
                        if (storage[i].registerType == mapFields.regType.holding && storage[i].parameterType == mapFields.paramType.float_t)
                        {
                            //storage[i].register = 12123;
                            storage[i].result_f = ModbusUtility.GetSingle(holdingRegistersRead[storage[i].baseRegister], holdingRegistersRead[storage[i].baseRegister + 1]);
                        }
                        else if (storage[i].registerType == mapFields.regType.input && storage[i].parameterType == mapFields.paramType.float_t)
                        {
                            storage[i].result_f = ModbusUtility.GetSingle(inputRegistersRead[storage[i].baseRegister], inputRegistersRead[storage[i].baseRegister + 1]);
                        }
                        else if (storage[i].registerType == mapFields.regType.holding && storage[i].parameterType == mapFields.paramType.word_t)
                        {
                            //storage[i].register = 12123;
                            storage[i].result_w = holdingRegistersRead[storage[i].baseRegister];
                        }
                        else if (storage[i].registerType == mapFields.regType.input && storage[i].parameterType == mapFields.paramType.word_t)
                        {
                            //storage[i].register = 12123;
                            storage[i].result_w = inputRegistersRead[storage[i].register - 30000];
                        }
                    }
                }
            }
        }
    }
}
